#ifndef ALLINONE_H
#define ALLINONE_H
#include <QCoreApplication>
#include <QThread>
#include <QUdpSocket>
#include <QHostAddress>
#include <QTextStream>
#include <QNetworkDatagram>
#ifdef __linux
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#else
#include <windows.h>
#endif
static const int tests = 10000;
static const int port = 23456;
static const int period = 0;
static const int batch = 100;
static const int packsize = 512;//pack size in 8 bytes
/*!
 * \brief test_Loop Test QUDPSocket using simple loop.
 * \return total send packages.
 */
inline int test_Loop()
{
	qint64 startID = -1, endID = -1;
	QUdpSocket * p = new QUdpSocket;
	char revdata[packsize*8];
	const long long * d = reinterpret_cast<const long long *>(revdata);
	if (p->bind(QHostAddress::LocalHost,port))
	{
		int i = 0;
		while (i<tests)
		{
			p->waitForReadyRead();
			while(p->hasPendingDatagrams() && i<tests)
			{
				int sz = p->readDatagram(revdata,packsize*8);
				if (sz<8)
					continue;
				if (d)
				{
					if (startID==-1)
						startID = *d;
					endID = *d;
				}
				++i;
			}

		}
		p->close();
	}
	p->deleteLater();
	return endID - startID + 1;
}

/*!
 * \brief The TestObj class test QUDPSocket using signal-slots.
 */
class TestObj:public QObject
{
	Q_OBJECT
public:
	explicit TestObj(QObject * pa = nullptr):QObject(pa){
		connect (this,&TestObj::_cmd_start,this,&TestObj::slot_start,Qt::QueuedConnection);
	}
	bool isFinished() const {return m_finished;}
	int startID() const {return m_startID;}
	int endID() const {return m_endID;}
protected slots:
	void readPdDt(){
		static char revdata[packsize*8];
		static const long long * d = reinterpret_cast<const long long *>(revdata);
		while (m_sock->hasPendingDatagrams())
		{
			int sz = m_sock->readDatagram(revdata,packsize*8);
			if (sz<8)
				continue;
			if (++m_total>tests)
			{
				m_sock->close();
				m_finished = true;
				QThread::currentThread()->quit();
				break;
			}
			if (d)
			{
				if (m_startID==-1)
					m_startID = *d;
				m_endID = *d;
			}
		}
	}
public:
	void start(){
		emit _cmd_start();
	}
private slots:
	void slot_start(){
		m_sock = new QUdpSocket(this);
		connect (m_sock,&QUdpSocket::readyRead,this,&TestObj::readPdDt);
		if (!m_sock->bind(QHostAddress::LocalHost,port))
		{
			m_sock->deleteLater();
			return;
		}
	}
signals:
	void _cmd_start();
private:
	int m_startID = -1;
	int m_endID = -1;
	int m_total = 0;
	bool m_finished = false;
	QUdpSocket * m_sock = nullptr;
};

#ifdef __linux
inline int local_test()
{

	int sockfd;
	char revdata[packsize*8];
	struct sockaddr_in servaddr, cliaddr;

	// 创建UDP套接字
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("socket creation failed");
		exit(EXIT_FAILURE);
	}
	int  rcvBufSize = 1024*1024*16;
	int optlen = sizeof(rcvBufSize);
	if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF,(char *) &rcvBufSize, optlen) < 0)
	{
		printf("setsockopt error=%d(%s)!!!\n", errno, strerror(errno));
	}
	if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF,(char *) &rcvBufSize, optlen) < 0)
	{
		printf("setsockopt error=%d(%s)!!!\n", errno, strerror(errno));
	}
	// 清零并设置服务器地址结构
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET; // IPv4
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 监听
	servaddr.sin_port = htons(port); // 端口号

	// 绑定套接字到指定端口
	if (bind(sockfd, (const struct sockaddr *)&servaddr,
			 sizeof(servaddr)) < 0) {
		perror("bind failed");
		close(sockfd);
		exit(EXIT_FAILURE);
	}

	socklen_t len = sizeof(cliaddr);
	int startID = -1, endID = -1;
	int total = 0;
	const long long * pL = reinterpret_cast<const long long *>(revdata);

	while (total<tests)
	{

		int rev = recvfrom(sockfd, (char *)revdata, packsize*8,
						   MSG_WAITALL, ( struct sockaddr *) &cliaddr,
						   &len);
		if(rev >=8)
		{
			if (startID==-1)
				startID = *pL;
			endID = *pL;
			++total;
		}
	}

	close(sockfd);

	return endID - startID + 1;
}

#else
/*!
 * \brief local_test test udp recv using local socket API
 * \return total send
 */
inline int local_test()
{
	//init wsa
	const WORD sockVision = MAKEWORD(2,2);
	WSADATA wsadata;
	//sockets
	SOCKET sock;
	if( WSAStartup(sockVision,&wsadata) != 0 )
	{
		printf("WSA initial failed.\n");
		return 0;
	}

	//Create sock
	sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
	if(sock == INVALID_SOCKET)
	{
		printf("socket creating failed.\n");
		return 0;
	}

	int  rcvBufSize = 1024*1024*16;
	int optlen = sizeof(rcvBufSize);
	if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,(char *) &rcvBufSize, optlen) < 0)
	{
		printf("setsockopt error=%d(%s)!!!\n", errno, strerror(errno));
	}
	if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF,(char *) &rcvBufSize, optlen) < 0)
	{
		printf("setsockopt error=%d(%s)!!!\n", errno, strerror(errno));
	}
	struct sockaddr_in sin;
	//Bind to Localhost:port
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//local host
	if( bind(sock,(LPSOCKADDR)&sin,sizeof(sin)) == SOCKET_ERROR )
	{
		printf("Can not bind to Address.\n");
		return 0;
	}
	struct sockaddr_in remoteAddr;
	int nAddrlen = sizeof(remoteAddr);
	int startID = -1, endID = -1;
	int total = 0;
	char revdata[packsize*8];
	const long long * pL = reinterpret_cast<const long long *>(revdata);
	while (total<tests)
	{
		int rev  = recvfrom(sock,revdata,packsize*8,0,(SOCKADDR*)&remoteAddr,&nAddrlen);
		if(rev >=8)
		{
			if (startID==-1)
				startID = *pL;
			endID = *pL;
			++total;
		}
	}
	closesocket(sock);
	WSACleanup();
	return endID - startID + 1;
}
#endif

/*!
 * \brief The sendingThread class send testing packages.
 */
class sendingThread: public QThread{
	Q_OBJECT
public:
	explicit sendingThread(QObject * pa = nullptr):QThread(pa){}
	void stop(){term = true;}
protected:
	void run() override{
		qint64 SendBuf[packsize] = {0};
		const char * dtp = reinterpret_cast<const char *>(SendBuf);

		QUdpSocket sock;
		QHostAddress addr ("127.0.0.1");


		//---------------------------------------------
		// Send a datagram to the receiver
		while(!term)
		{
			for (int i=0;i<batch;++i)
			{
				const int BufLen = sizeof(SendBuf) - rand()%(sizeof(SendBuf)-100);
				++SendBuf[0];
				sock.writeDatagram(dtp, BufLen,addr,port);
			}
			if (period>0)
				msleep(period);
		}
		//---------------------------------------------
		// When the application is finished sending, close the socket.
		wprintf(L"Finished sending. Closing socket.\n");

		//---------------------------------------------
		// Clean up and quit.
		wprintf(L"Exiting.\n");

	}
private:
	bool term = false;
};
#endif // ALLINONE_H
